package org.yanh.es.utils;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.client.indices.*;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.reindex.BulkByScrollResponse;
import org.elasticsearch.index.reindex.DeleteByQueryRequest;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@Component
@Slf4j
public class EsUtils<T> {
    @Autowired
    private RestHighLevelClient client;

    /**
     * 获取分词结果
     * @param analyzer
     * @param text
     * @return
     * @throws IOException
     */
    public List<AnalyzeResponse.AnalyzeToken> analyzeRequest(String analyzer,String text) throws IOException {
        AnalyzeRequest analyzeRequest = AnalyzeRequest.withGlobalAnalyzer(analyzer, text);
        AnalyzeResponse response =  client.indices().analyze(analyzeRequest, RequestOptions.DEFAULT);
        List<AnalyzeResponse.AnalyzeToken> tokens = response.getTokens();
        return tokens;
    }

    /**
     * 判断索引是否存在
     *
     * @param index
     * @return
     * @throws IOException
     */
    public boolean existsIndex(String index) throws IOException {
        GetIndexRequest request = new GetIndexRequest(index);
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        return exists;
    }

    /**
     * 创建索引
     *
     * @param index
     * @throws IOException
     */
    public boolean createIndex(String index) throws IOException {
        CreateIndexRequest request = new CreateIndexRequest(index);
        CreateIndexResponse createIndexResponse = client.indices()
                .create(request, RequestOptions.DEFAULT);
        return createIndexResponse.isAcknowledged();
    }

    /**
     * 获取索引映射
     *
     * @param index
     * @return
     * @throws IOException
     */
    public String getIndexMap(String index) throws IOException {
        GetMappingsRequest getMappingsRequest = new GetMappingsRequest();
        getMappingsRequest.indices(index);
        GetMappingsResponse mapping = client.indices().getMapping(getMappingsRequest, RequestOptions.DEFAULT);
        Map<String, MappingMetadata> mappings = mapping.mappings();
        MappingMetadata metadata = mappings.get(index);
        return metadata.getSourceAsMap().toString();
    }

    /**
     * 删除索引
     *
     * @param index
     * @return
     * @throws IOException
     */
    public boolean deleteIndex(String index) throws IOException {
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(index);
        AcknowledgedResponse response = client.indices()
                .delete(deleteIndexRequest, RequestOptions.DEFAULT);
        return response.isAcknowledged();
    }

    /**
     * 清空索引内容
     *
     * @param indices 索引名称
     * @throws IOException
     * @return
     */
    public Long deleteAllByIndices(String indices) {
        long start = System.currentTimeMillis();
        log.info(" = = = >>> 索引库 {} 删除全部数据", indices);
        DeleteByQueryRequest request = new DeleteByQueryRequest(indices);
        // 异常继续执行
        request.setConflicts("proceed");
        request.setQuery(new BoolQueryBuilder().filter(QueryBuilders.boolQuery()));
        BulkByScrollResponse response = null;
        try {
            response = client.deleteByQuery(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info(" = = = >>> 索引库 {} 删除全部数据, 条数: {}, 耗时: {}/秒", indices,
                response.getDeleted(), (System.currentTimeMillis() - start) / 1000);
        return response.getDeleted();
    }

    /**
     * 判断某索引下文档id是否存在
     *
     * @param index
     * @param id
     * @return
     * @throws IOException
     */
    public boolean docExists(String index, String id) throws IOException {
        GetRequest getRequest = new GetRequest(index, id);
        //只判断索引是否存在不需要获取_source
        getRequest.fetchSourceContext(new FetchSourceContext(false)).storedFields("_none_");
        boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
        return exists;
    }

    /**
     * 添加文档记录
     *
     * @param index
     * @param id
     * @param t 要添加的数据实体类
     * @return
     * @throws IOException
     */
    public boolean addDoc(String index, String id, T t) throws IOException {
        IndexRequest request = new IndexRequest(index);
        request.id(id);
        //timeout
        request.timeout(TimeValue.timeValueSeconds(1));
        request.timeout("1s");
        request.source(JSON.toJSONString(t), XContentType.JSON);
        IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
        RestStatus Status = indexResponse.status();
        return Status == RestStatus.OK || Status == RestStatus.CREATED;
    }

    /**
     * 根据id来获取记录
     *
     * @param index
     * @param id
     * @return
     * @throws IOException
     */
    public GetResponse getDoc(String index, String id) throws IOException {
        GetRequest request = new GetRequest(index, id);
        GetResponse getResponse = client.get(request,RequestOptions.DEFAULT);
        return getResponse;
    }

    /**
     * 批量添加文档记录
     * 没有设置id ES会自动生成一个，如果要设置 IndexRequest的对象.id()即可
     *
     * @param index
     * @param list
     * @return
     * @throws IOException
     */
    public boolean bulkAdd(String index, List<T> list) throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        //timeout
        bulkRequest.timeout(TimeValue.timeValueMinutes(2));
        bulkRequest.timeout("2m");
        for (int i = 0; i < list.size(); i++) {
            bulkRequest.add(new IndexRequest(index).source(JSON.toJSONString(list.get(i)),XContentType.JSON));
        }
        BulkResponse bulkResponse = client.bulk(bulkRequest,RequestOptions.DEFAULT);
        return !bulkResponse.hasFailures();
    }

    /**
     * 更新文档记录
     * @param index
     * @param id
     * @param t
     * @return
     * @throws IOException
     */
    public boolean updateDoc(String index, String id, T t) throws IOException {
        UpdateRequest request = new UpdateRequest(index, id);
        request.doc(JSON.toJSONString(t),XContentType.JSON);
        request.timeout(TimeValue.timeValueSeconds(1));
        request.timeout("1s");
        UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT);
        return updateResponse.status() == RestStatus.OK;
    }

    /**
     * 删除文档记录
     *
     * @param index
     * @param id
     * @return
     * @throws IOException
     */
    public boolean deleteDoc(String index, String id) throws IOException {
        DeleteRequest request = new DeleteRequest(index, id);
        //timeout
        request.timeout(TimeValue.timeValueSeconds(1));
        request.timeout("1s");
        DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
        return deleteResponse.status() == RestStatus.OK;
    }

    /**
     * 批量删除ES
     * @param indexName 索引名称
     * @param ids id列表
     * @return
     */
    public boolean deleteDocBatch(String indexName,List<String> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return false;
        }
        BulkRequest bulkRequest = packBulkDeleteRequest(indexName, ids);
        try {
            BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulk.hasFailures()) {
                for (BulkItemResponse item : bulk.getItems()) {
                    log.error("删除索引:[{}],主键：{}失败，信息：{}",indexName,item.getId(),item.getFailureMessage());
                }
                return false;
            }
            //记录索引新增与修改数量
            Integer deleteCount = 0;
            for (BulkItemResponse item : bulk.getItems()) {
                if (DocWriteResponse.Result.DELETED.equals(item.getResponse().getResult())) {
                    deleteCount++;
                }
            }
            log.info("批量删除索引[{}]成功，共删除[{}]个",indexName,deleteCount);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            log.error("删除索引：【{}】出现异常:{}",indexName,e);
            return false;
        }
    }

    /**
     * 组装删除操作
     * @param indexName 索引名称
     * @param ids id列表
     * @return
     */
    private BulkRequest packBulkDeleteRequest(String indexName, List<String> ids) {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
        ids.forEach(id -> {
            DeleteRequest deleteRequest = new DeleteRequest(indexName);
            deleteRequest.id(id);
            bulkRequest.add(deleteRequest);
        });
        return bulkRequest;
    }
    /**
     * 查询索引库条数
     *
     * @param index 索引库名称
     * @param boolQueryBuilder 条件对象
     */
    public Long searchIndexDocCount(String index, BoolQueryBuilder boolQueryBuilder) {
        long start = System.currentTimeMillis();
        log.info(" = = = >>> 查询索引库 {} 总条数", index);
        CountRequest countRequest = new CountRequest();
        countRequest.indices(index);
        if (boolQueryBuilder != null) {
            countRequest.query(boolQueryBuilder);
        }
        CountResponse count = null;
        try {
            count = client.count(countRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        log.info(" = = = >>> 索引库 {} 的总条数: {} , 耗时: {}/秒", index,
                count.getCount(), (System.currentTimeMillis() - start) / 1000);
        return count.getCount();
    }

    //    private XContentBuilder generateBuilder(Class clazz) {
//        XContentBuilder builder = null;
//        try {
//            builder = XContentFactory.jsonBuilder();
//            builder.startObject();
//            builder.startObject("properties");
//            java.lang.reflect.Field[] declaredFields = clazz.getDeclaredFields();
//            for (java.lang.reflect.Field f : declaredFields) {
//                if (f.isAnnotationPresent(EsField.class)) {
//                    // 获取注解
//                    EsField declaredAnnotation = f.getDeclaredAnnotation(EsField.class);
//                    if (declaredAnnotation.type() == EsFieldEnum.OBJECT) {
//                        // 获取当前类的对象-- Action
//                        Class<?> type = f.getType();
//                        java.lang.reflect.Field[] df2 = type.getDeclaredFields();
//                        builder.startObject(f.getName());
//                        builder.startObject("properties");
//                        // 遍历该对象中的所有属性
//                        for (java.lang.reflect.Field f2 : df2) {
//                            if (f2.isAnnotationPresent(EsField.class)) {
//                                // 获取注解
//                                EsField declaredAnnotation2 = f2.getDeclaredAnnotation(EsField.class);
//                                builder.startObject(f2.getName());
//                                builder.field("type", declaredAnnotation2.type().getType());
//                                // keyword不需要分词
//                                if (declaredAnnotation2.type() == EsFieldEnum.TEXT) {
//                                    builder.field("analyzer", declaredAnnotation2.analyzer().getType());
//                                }
//                                // date格式化
//                                if (declaredAnnotation.type() == EsFieldEnum.DATE) {
//                                    if (declaredAnnotation.format()!= DateformatEnum.NO)
//                                    {
//                                        var format=declaredAnnotation.format().getType();
//                                        builder.field("format", format);
//                                    }
//                                }
//                                builder.endObject();
//                            }
//                        }
//                        builder.endObject();
//
//                    } else {
//                        builder.startObject(f.getName());
//                        builder.field("type", declaredAnnotation.type().getType());
//                        // keyword不需要分词
//                        if (declaredAnnotation.type() == EsFieldEnum.TEXT) {
//                            builder.field("analyzer", declaredAnnotation.analyzer().getType());
//                        }
//                        // date格式化
//                        if (declaredAnnotation.type() == EsFieldEnum.DATE) {
//                            if (declaredAnnotation.format()!=DateformatEnum.NO)
//                            {
//                                var format=declaredAnnotation.format().getType();
//                                builder.field("format", format);
//                            }
//                        }
//                    }
//                    builder.endObject();
//                }
//            }
//            // 对应property
//            builder.endObject();
//            builder.endObject();
//        } catch (IOException e) {
//            log.error("【ES操作】 组装映射字段语句异常");
//        }
//
//        return builder;
//    }
//
//    /**
//     * 对象创建索引和映射
//     *
//     * @param indexName
//     * @return
//     * @throws IOException
//     */
//    public boolean createIndexAndMap(String indexName, Class clazz) throws IOException {
//        if (existsIndex(indexName)) {
//            log.info("索引={}已存在", indexName);
//            return false;
//        }
//        CreateIndexRequest request = new CreateIndexRequest(indexName);
//        request.settings(Settings.builder()
//                .put("index.number_of_shards", 3)
//                .put("index.number_of_replicas", 3)
//        );
//        request.mapping(generateBuilder(clazz));
//        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
//        boolean acknowledged = response.isAcknowledged();
//        boolean shardsAcknowledged = response.isShardsAcknowledged();
//        return acknowledged || shardsAcknowledged;
//    }
}